package config_center

import (
	"context"
	"errors"
	jsoniter "github.com/json-iterator/go"
	"google.golang.org/protobuf/proto"
	"gorm.io/gorm"
	"os"
	"payme3000/framework/db"
	"payme3000/framework/db/model"
	"payme3000/framework/errorx"
	"payme3000/framework/util/file"
	"payme3000/framework/util/math"
	"sync"
	"time"
)

type (
	// ConfigCenter 基于db的配置中心
	// 配置中心会托管所有可访问资源, 如mysql/postgresql/mongodb/redis/kafka等
	ConfigCenter struct {
		appEnv     string
		dbConfPath string // db.json配置文件路径

		kafkaReactor IKafkaReactor // kafka管理器
		dbReactor    IDbReactor    // db管理器

		configItemMap  map[string]*ConfigItem // 观察者列表, 配置中心会持续监听观察者指定的key的变更
		configItemLock sync.RWMutex
		configItemOnce sync.Once
	}

	IDbReactor interface {
		GetDefaultConfigDbType() db.Type
		GetDbConfigKey(db.Type) string
		GetMySqlMin() db.Type
		GetMySqlMax() db.Type
		GetPostgresqlMin() db.Type
		GetPostgresqlMax() db.Type
		GetRedisMin() db.Type
		GetRedisMax() db.Type
		GetMongoDbMin() db.Type
		GetMongoDbMax() db.Type
		GetEtcdMin() db.Type
		GetEtcdMax() db.Type
	}
)

func NewConfigCenter(path string, dbReactor IDbReactor) *ConfigCenter {
	return &ConfigCenter{
		dbConfPath:    path,
		dbReactor:     dbReactor,
		configItemMap: map[string]*ConfigItem{},
	}
}

// Start 初始化, 必须连接上配置中心的资料库
func (c *ConfigCenter) Start() error {
	if err := c.initDb(c.dbReactor.GetDefaultConfigDbType()); err != nil {
		return err
	}

	if appEnv, _ := c.Get("app_env"); appEnv != nil {
		c.appEnv = *appEnv
	}

	return nil
}

func (c *ConfigCenter) Stop() {
	// 释放资源
	db.StopGorm()
	db.StopMongoDb()
	db.StopRedis()
	db.StopEtcd()
}

// Get 获取资料库里配置项的值, 返回的是指针, 如果是nil代表资料库没有这个配置项
func (c *ConfigCenter) Get(key string) (*string, error) {
	configItem := &model.Config{}

	if err := c.GetGorm(c.dbReactor.GetDefaultConfigDbType()).
		Where("key_str = ?", key).
		Take(configItem).Error; err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return nil, nil
		}

		return nil, errorx.Throw(context.Background(), err)
	}

	return proto.String(configItem.ValueStr), nil
}

func (c *ConfigCenter) GetAppEnv() string {
	return c.appEnv
}

func (c *ConfigCenter) SetKafkaReactor(kafkaReactor IKafkaReactor) {
	c.kafkaReactor = kafkaReactor
}

func (c *ConfigCenter) SetDbReactor(dbReactor IDbReactor) {
	c.dbReactor = dbReactor
}

// initDb 初始化db资源实例
func (c *ConfigCenter) initDb(dbType db.Type) error {
	if dbType == c.dbReactor.GetDefaultConfigDbType() {
		// 配置中心基于该连接运作, 需从配置文件获取连接配置
		if !file.Exist(c.dbConfPath) {
			return errorx.New(context.Background(), "db config file not found")
		}

		// config center的资料库, 需要通过配置文件获取连接配置, 且不需要监听
		data, err := os.ReadFile(c.dbConfPath)
		if err != nil {
			return errorx.Throw(context.Background(), err)
		}

		dbConfigMap := map[string]*db.Config{}
		// 读取的数据为json格式，需要进行解码
		if err = jsoniter.Unmarshal(data, &dbConfigMap); err != nil {
			return errorx.Throw(context.Background(), err)
		}
		key := c.dbReactor.GetDbConfigKey(dbType)
		dbConfig := dbConfigMap[key]

		if math.InRange(dbType, c.dbReactor.GetMySqlMin(), c.dbReactor.GetMySqlMax()) {
			if err := db.CreateGormMysql(dbType, dbConfig); err != nil {
				_ = errorx.Errorf(context.Background(), "create gorm error with key:%s, dbType:%d, dbConfig:%+v", key, dbType, dbConfig)
				return err
			}
		} else if math.InRange(dbType, c.dbReactor.GetPostgresqlMin(), c.dbReactor.GetPostgresqlMax()) {
			if err := db.CreateGormPostgresql(dbType, dbConfig); err != nil {
				_ = errorx.Errorf(context.Background(), "create gorm error with key:%s, dbType:%d, dbConfig:%+v", key, dbType, dbConfig)
				return err
			}
		}

		return nil
	}

	// 其余配置通过配置表获取

	key := c.dbReactor.GetDbConfigKey(dbType)
	if key == "" {
		return errorx.Errorf(context.Background(), "empty db config key with %d", dbType)
	}

	// 监听这个key
	if err := c.Watch(&ConfigItem{
		Key:   key,
		Value: NewAtomicString(""),
		NotifyFunc: func(newValue IAtomic) {
			dbConfig := &db.Config{}
			if err := jsoniter.Unmarshal([]byte(newValue.String()), dbConfig); err != nil {
				errorx.Catch(context.Background(), err)
				return
			}

			if err := c.createDb(dbType, dbConfig); err != nil {
				_ = errorx.Errorf(context.Background(), "create db error with key:%s, dbType:%d, dbConfig:%+v", key, dbType, dbConfig)
			}
		},
	}); err != nil {
		return err
	}

	return nil
}

// createDb 创建db资源实例
func (c *ConfigCenter) createDb(dbType db.Type, config *db.Config) error {
	if math.InRange(dbType, c.dbReactor.GetMySqlMin(), c.dbReactor.GetMySqlMax()) {
		return db.CreateGormMysql(dbType, config)
	} else if math.InRange(dbType, c.dbReactor.GetPostgresqlMin(), c.dbReactor.GetPostgresqlMax()) {
		return db.CreateGormPostgresql(dbType, config)
	} else if math.InRange(dbType, c.dbReactor.GetRedisMin(), c.dbReactor.GetRedisMax()) {
		return db.CreateRedis(dbType, config)
	} else if math.InRange(dbType, c.dbReactor.GetMongoDbMin(), c.dbReactor.GetMongoDbMax()) {
		return db.CreateMongoDb(dbType, config)
	} else if math.InRange(dbType, c.dbReactor.GetEtcdMin(), c.dbReactor.GetEtcdMax()) {
		return db.CreateEtcd(dbType, config)
	}

	return errorx.New(context.Background(), "illegal db type")
}

// monitor 监控所有配置项的变更
func (c *ConfigCenter) monitor() {
	for {
		var keys []string

		// 收集正在监控的配置项
		func() {
			c.configItemLock.RLock()
			defer c.configItemLock.RUnlock()

			for key := range c.configItemMap {
				keys = append(keys, key)
			}
		}()

		if len(keys) > 0 {
			// 查询目前资料库里的配置值
			var dbConfigItemList []*model.Config

			if err := c.GetGorm(c.dbReactor.GetDefaultConfigDbType()).
				Where("key_str IN ?", keys).
				Find(&dbConfigItemList).Error; err != nil {
				errorx.Catch(context.Background(), err)
			} else if len(dbConfigItemList) > 0 {
				func() {
					c.configItemLock.RLock()
					defer c.configItemLock.RUnlock()

					for _, dbConfigItem := range dbConfigItemList {
						if configItem := c.configItemMap[dbConfigItem.KeyStr]; configItem != nil &&
							configItem.Value.String() != dbConfigItem.ValueStr {
							if configItem.Value.Store(dbConfigItem.ValueStr) {
								configItem.Notify()
							} else {
								_ = errorx.Errorf(context.Background(), "config item value store error with key:%s", dbConfigItem.KeyStr)
							}
						}
					}
				}()
			}
		}

		time.Sleep(1 * time.Minute)
	}
}
